import { readonly } from "./readonly"

export type UserIdentifier = string
export type EntityIdentifier = number
export type UniqueIdentifier = string
export type ByteArray = string
export type EnumField= string

export interface Record {
  id: EntityIdentifier
}

export interface VersionedRecord extends Record {
  recordVersion:number
}

export interface StoreFeatures {
  versioned?:boolean
  stringId?:boolean
  autoId?:boolean
}

export const defaultStoreFeatures:StoreFeatures = {
  stringId:false,
  autoId:true
}

export interface StoreTransactionManager {
  tranBegin():Promise<void>
  tranCommit():Promise<void>
  tranRollback():Promise<void>
}
export const defaultStoreTransactionManager:StoreTransactionManager = {
  async tranBegin():Promise<void> {

  },
  async tranCommit():Promise<void> {

  },
  async tranRollback():Promise<void> {}
}

export interface Store<TEntity extends Record> {
  features:StoreFeatures
  //#region general database operations
  transactionManager:StoreTransactionManager
  insert(entity:TEntity, fields?:Array<keyof TEntity>):Promise<TEntity>
  find(predicate: {(entity:TEntity):boolean}):Promise<TEntity[]>
  update(entity:TEntity, fields?:Array<keyof TEntity>,predicate?:{(entity:TEntity):boolean}):Promise<number>
  delete(predicate?:{(entity:TEntity):boolean}):Promise<number>
  //#endregion

  //#region entity operations
  get(id:EntityIdentifier|{(entity:TEntity):boolean}):Promise<TEntity|undefined>
  add(entity:TEntity, fields?:Array<keyof TEntity>):Promise<TEntity>
  save(entity:TEntity, fields?:Array<keyof TEntity>,predicate?:{(entity:TEntity):boolean}):Promise<TEntity|undefined>
  remove(entity:TEntity):Promise<TEntity>
  addOrSave(entity:TEntity, fields?:Array<keyof TEntity>):Promise<TEntity>
  //#endregion
}
const TRANSACTION_MANAGER = Symbol('transactionManager')
export class DefaultStore<TEntity extends Record> implements Store<TEntity> {
  private entities:TEntity[]
  [TRANSACTION_MANAGER]:StoreTransactionManager
  private idSeed:number = 0
 
  constructor(entities?:TEntity[],transactionManager?:StoreTransactionManager) {
    this[TRANSACTION_MANAGER] = transactionManager ?? defaultStoreTransactionManager
    if (entities) {
      this.entities = entities.map(e=>({...e}))
      if (this.features.autoId) {
        this.idSeed = Math.max(...entities.map(e=>e.id)) ?? 0
      }
    }else {
      this.entities = []
      if (this.features.autoId) {
        this.idSeed = 0
      }
    }
  }
  get features():StoreFeatures {
    return defaultStoreFeatures
  }

  get transactionManager():StoreTransactionManager {
    return this[TRANSACTION_MANAGER]
  }

  

  async insert(entity:TEntity, fields?:Array<keyof TEntity>):Promise<TEntity> {
    let newEntity = {} as TEntity
    if (fields) {
      
      for (const field of fields) {
        newEntity[field] = entity[field]
      }
      
    }else {
      newEntity = {...entity}
    }
    if (this.features.autoId) {
      entity.id = newEntity.id = ++this.idSeed
    }else {
      if (this.entities.some(e=>e.id === entity.id)) {
        throw new Error(`Entity with id ${entity.id} already exists`)
      }
      newEntity.id = entity.id
    }
    this.entities.push(newEntity)
    return entity
  }

  async find(predicate: {(entity:TEntity):boolean}):Promise<TEntity[]> {
    const found:TEntity[] = []
    for (const entity of this.entities) {
      if (predicate(readonly(entity))) {
        found.push({...entity})
      }
    } 
    return found
  }

  async update(entity:TEntity, fields?:Array<keyof TEntity>,predicate?:{(entity:TEntity):boolean}):Promise<number> {
    let updated = 0
    for(const record of this.entities) {
      if (predicate) {
        if (!predicate(readonly(record))) continue
      }
      updated++
      if (fields) {
        for (const field of fields) {
          record[field] = entity[field] as TEntity[keyof TEntity]
        }
      }else { 
        for (const field of Object.keys(entity)) {
          record[field as keyof TEntity] = entity[field as keyof TEntity] as TEntity[keyof TEntity]
        }
      }
    }
    return updated
  }

  async delete(predicate?:{(entity:TEntity):boolean}):Promise<number> {
    if (!predicate) {
      this.entities = []
      return this.entities.length
    }
    let c = 0
    for (let i = 0,j = this.entities.length; i < j; i++) {
      const entity = this.entities.shift() as TEntity
      if (!predicate(readonly(entity))) {
        this.entities.push(entity)
      }else {
        c++
      } 
    }
    return c
  }
  
  

  async get(id:EntityIdentifier|{(entity:TEntity):boolean}):Promise<TEntity|undefined> {
    if  (typeof id === 'function') {
      const rs = await this.find(id)
      return rs[0]
    }else {
      const rs = await this.find(e=>e.id === id)
      return rs[0]
    }
  }

  async add(entity:TEntity, fields?:Array<keyof TEntity>):Promise<TEntity> {
    await this.insert(entity, fields)
    return entity
  }

  async save(entity:TEntity, fields?:Array<keyof TEntity>,predicate?:{(entity:TEntity):boolean}):Promise<TEntity|undefined> {
    if (predicate) {
      if(await this.update(entity, fields,(existed)=>predicate(existed) && existed.id === entity.id)) {
        return entity
      }
    }else {
      if (await this.update(entity, fields,(existed)=>existed.id === entity.id)) 
        return entity

    }
    return undefined
  }

  async addOrSave(entity:TEntity, fields?:Array<keyof TEntity>):Promise<TEntity> {
    if (entity.id) {
      const updated = await this.update(entity, fields,(existed)=>existed.id === entity.id)
      if (updated === 0) {
        await this.insert(entity, fields)
      }
    }else {
      await this.insert(entity, fields)
    }
    return entity
  }

  async remove(entity:TEntity):Promise<TEntity> {
    await this.delete(e=>e.id === entity.id)
    return entity
  }
}

export interface VersionedStore<TEntity extends VersionedRecord> extends Store<TEntity> {
  
}

export class DefaultVersionedStore<TEntity extends VersionedRecord> extends DefaultStore<TEntity> implements VersionedStore<TEntity> {
  constructor(entities?:TEntity[],transactionManager?:StoreTransactionManager) {
    super(entities,transactionManager)
  }
  async add(entity:TEntity, fields?:Array<keyof TEntity>):Promise<TEntity> {
    entity.recordVersion = 1
    if (fields) {
      fields.push('recordVersion')
    }
    await this.insert(entity, fields)
    return entity
  }
  async save(entity:TEntity, fields?:Array<keyof TEntity>,predicate?:{(entity:TEntity):boolean}):Promise<TEntity|undefined> {
    if (predicate) {
      return await super.save(entity, fields,(existed)=>predicate(existed) && existed.recordVersion === entity.recordVersion)
    }else {
      return await super.save(entity, fields,(existed)=>existed.recordVersion === entity.recordVersion)
    }
  }

  async addOrSave(entity:TEntity, fields?:Array<keyof TEntity>):Promise<TEntity> {
    throw new Error('Not allowed for versioned store')
  }
}
